#pragma once
#include <Dynamics/force_module.h>
#include <DataCore/sim_data.h>
#include <Dynamics/Material/FemMaterial/fem_material.h>
#include <Geometry/ele_type.h>

namespace PhysLeo {

/**
 * FEM cpu model, extends the ForceModule interface.
 * implement the classical finite element method for simulation. For non-constant strain element, use the gauss quadrature method.
 * you can choose different numbers of gauss quadrature points. 
 */
template<typename T>
class FemCpu : public ForceModule<T> {
public:
    /**
     * empty constructor, set everything to its default value
     */
    FemCpu();

    /**
     * allocate resource.
     * @param[in] simData  a data structure manage the simulation data. 
     */
    void allocateResource(SimData<T>& simData) override;

    /**
    * return the deformation energy by FEM.
    * @param[in] ptr_pos  current position of the system
    * @return deformation energy according to current position
    */
    T deformEnergy(std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_pos) override;

    /**
    * return the internal force by FEM.
    * @param[in] ptr_pos  current position of the system
    * @param[in] ptr_force  internal force of the system according to current position.
    */
    void internalForce(std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_pos, std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_force) override;

protected:
    /**
    * precompute the shape matrix for cubic element, store it in ptr_shape_matrix_
    */
    virtual void cubicShapeMatrix();

    /**
    * precompute the shape matrix for tet element, store it in ptr_shape_matrix_
    */
    virtual void tetShapeMatrix();

    /**
    * return the internal force by FEM cubic elements.
    * @param[in] ptr_pos  current position of the system
    * @param[in] ptr_force  internal force of the system according to current position.
    */
    virtual void cubicInternalForce(std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_pos, std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_force);

    /**
    * return the internal force by FEM tet elements.
    * @param[in] ptr_pos  current position of the system
    * @param[in] ptr_force  internal force of the system according to current position.
    */
    virtual void tetInternalForce(std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_pos, std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_force);

    //shared resources, will be already set after allocateResource function.

    /**
    * rest position
    */
    std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_rest_pos_;

    /**
    * current position
    */
    std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_pos_;

    /**
    * internal force
    */
    std::shared_ptr<BufferData<glm::tvec3<T>>> ptr_force_;

    /**
    * element vertices index
    */
    std::shared_ptr<BufferData<int>> ptr_ele_;

    /**
    * element volume
    */
    std::shared_ptr<BufferData<T>> ptr_ele_volume_;

    /**
    * element material
    */
    std::shared_ptr<BufferData<T>> ptr_ele_material_;

    //self resources, take care about their allocation and releasion. 

    /**
     * number of  gauss quadrature points, it can be set 1 or 2 now.
     */
    int quadrature_points_;

    /**
    * FEM material type 
    */
    FemMaterialType fem_material_type_;

    /**
    * whether the material is homogeneous
    */
    bool homogeneous_;

    /**
    * element type
    */
    EleType ele_type_;

    /**
    * shape matrix, allocate when module initialize, release when module destruct.
    * shape matrix could be 2x2 3x3 3x4 3x8 matrix, but they will all be stored in a BufferData<T> form.
    * there need to do manual convert between this storage form and its true type.
    */
    std::shared_ptr<BufferData<T>> ptr_shape_matrix_;
};

}